﻿
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using System.Text;

namespace TcpSocketTaskDemo.Echo
{
    sealed class Receiptor : IDisposable
    {
        #region "member fields"

        private readonly AwaitSocket m_awaitSocket;
        private bool m_disposed;

        #endregion

        #region "constructor and destructor"

        public Receiptor(Socket socket)
        {
            m_awaitSocket = new AwaitSocket(socket, 1024);
        }

        ~Receiptor()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (m_disposed)
                return;

            try
            {
                if (disposing)
                {
                    m_awaitSocket.Dispose();
                }
            }
            finally
            {
                m_disposed = true;
            }
        }
        #endregion

        #region "public API"

        public void Receive()
        {
            Task<Tuple<byte[], int>> recvTask = m_awaitSocket.Receive();

            recvTask.ContinueWith(this.OnSuccessRecved, TaskContinuationOptions.OnlyOnRanToCompletion);
            recvTask.ContiExcepToDispose(this);
        }

        #endregion

        #region "private helpers"

        private void OnSuccessRecved(Task<Tuple<byte[], int>> recvTask)
        {
            Tuple<byte[], int> recved = recvTask.Result;

            string echoString = string.Format("server echo: {0}",
                                              Encoding.ASCII.GetString(recved.Item1, 0, recved.Item2));
            var sendTask = m_awaitSocket.Send(Encoding.ASCII.GetBytes(echoString));

            sendTask.ContinueWith(prevTask => Receive(), TaskContinuationOptions.OnlyOnRanToCompletion);
            sendTask.ContiExcepToDispose(this);
        }

        #endregion
    }

    sealed class Server : IDisposable
    {
        #region "member fields"

        private readonly Socket m_lsnSocket;
        private readonly IPEndPoint m_lsnEndpnt;
        private bool m_disposed;

        #endregion

        #region "constructor and destructor"

        public Server(IPEndPoint endpoint)
        {
            m_lsnEndpnt = new IPEndPoint(IPAddress.Any, endpoint.Port);
            m_lsnSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        ~Server()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (m_disposed)
                return;

            try
            {
                if (disposing)
                {
                    m_lsnSocket.Close();
                }
            }
            finally
            {
                m_disposed = true;
            }
        }
        #endregion

        #region "public API"

        public void Start()
        {
            m_lsnSocket.Bind(m_lsnEndpnt);
            m_lsnSocket.Listen(10);

            Accept();
        }

        #endregion

        #region "private helpers"

        private void Accept()
        {
            Task<Socket> acceptTask = Task<Socket>.Factory.FromAsync(
                m_lsnSocket.BeginAccept,
                m_lsnSocket.EndAccept,
                null);

            acceptTask.ContinueWith(prevTask =>
                                        {
                                            Socket clientSocket = prevTask.Result;
                                            Console.WriteLine("client received from <{0}>", clientSocket.RemoteEndPoint);

                                            new Receiptor(clientSocket).Receive();

                                            Accept();
                                        }, TaskContinuationOptions.OnlyOnRanToCompletion);
            acceptTask.ContiExcepToDispose(this);
        }

        #endregion
    }
}
